Skip to content

feat(ui,clerk-js,shared): Add TXT domain verification to self-serve SSO#8788

Open
LauraBeatris wants to merge 23 commits into
mainfrom
laura/orgs-1594-sdk-add-txt-domain-verification-step
Open

feat(ui,clerk-js,shared): Add TXT domain verification to self-serve SSO#8788
LauraBeatris wants to merge 23 commits into
mainfrom
laura/orgs-1594-sdk-add-txt-domain-verification-step

Conversation

@LauraBeatris

@LauraBeatris LauraBeatris commented Jun 9, 2026

Copy link
Copy Markdown
Member

Description

Introduce a new step to create organization domains and verify them with TXT verification. This step is required before selecting a provider, and all domains must get verified before proceesing.

CleanShot.2026-06-12.at.17.12.48.mp4

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Add/manage organization SSO domains (add, list, remove) with strict domain validation, TXT ownership verification, bulk ownership verification flow, realtime polling until verified, and an Enterprise SSO badge.
  • UX

    • SSO setup wizard now uses organization domains as the verification gate (skips verify step when all domains verified); Continue only when domains verified; domain suggestions when none exist.
  • Localization

    • Updated domain-configuration copy across ~44 locales; pluralized “Verify domain” → “Verify domains”.

@LauraBeatris LauraBeatris self-assigned this Jun 9, 2026
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 12, 2026 9:04pm
swingset Ready Ready Preview, Comment Jun 12, 2026 9:04pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fe8b686

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@clerk/localizations Minor
@clerk/clerk-js Minor
@clerk/shared Minor
@clerk/ui Minor
@clerk/react Patch
@clerk/chrome-extension Patch
@clerk/expo Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds TXT-based organization domain ownership verification: new DTOs and org APIs, cache keys and internal hook with polling, OrganizationDomainsStep UI and validators, ConfigureSSO/context rewiring to pass domain mutations, appearance/localization updates across locales, and updated tests.

Changes

Organization domain TXT verification

Layer / File(s) Summary
Shared DTOs and localization types
packages/shared/src/types/*
Adds ownership verification JSON types, extends OrganizationDomain JSON with affiliation/ownership fields, and updates LocalizationResource shape for organizationDomainsStep and enterprise SSO badge.
clerk-js resources
packages/clerk-js/src/core/resources/*
Organization.getDomains supports enrollmentMode; createDomain accepts enrollmentMode; bulk prepare/attempt ownership verification endpoints added; OrganizationDomain parses affiliation and ownership verification payloads.
Stable keys & hook exports
packages/shared/src/react/stable-keys.ts, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/useOrganizationDomains.shared.ts
New ORGANIZATION_DOMAINS_KEY and useOrganizationDomains cache-key helper; re-exports internal useOrganizationDomains and its types.
useOrganizationDomains implementation
packages/shared/src/react/hooks/useOrganizationDomains.tsx
Implements domain listing, createDomain (enterprise_sso path selects prepared verification), prepare/attempt ownership verification, revalidate, and polling of outstanding ownership verification attempts.
ConfigureSSO context & wiring
packages/ui/src/components/ConfigureSSO/*, packages/ui/src/components/OrganizationProfile/*
Context and provider now carry enterpriseConnectionMutations and organizationDomainMutations; primary-email derived flows removed; ConfigureSSO/OrganizationSecurity pass organizationDomains and domain mutation handlers into the wizard.
OrganizationDomainsStep UI
packages/ui/src/components/ConfigureSSO/steps/OrganizationDomainsStep.tsx
New wizard step with domain input, strict domain validator, suggestion from user email, domain cards showing TXT instructions/verified timestamp, create/delete flows, and Continue gating on ownershipVerification status.
Wizard & step updates
packages/ui/src/components/ConfigureSSO/steps/*
Wizard now renders OrganizationDomainsStep for verification; SelectProvider forwards domain names to createConnection; VerifyDomainStep removed; various steps read mutations from enterpriseConnectionMutations.
Localization & appearance
packages/localizations/src/*, packages/ui/src/customizables/*, packages/ui/src/internal/*
~40 locale files replace verifyEmailDomainStep with organizationDomainsStep; en-US adds enterprise SSO badge; new configureSSOVerifyDomain appearance keys and element selectors added.
Tests
packages/ui/src/components/ConfigureSSO/__tests__/*, packages/ui/src/components/ConfigureSSO/steps/__tests__/*
Tests updated to mock organization domains, assert wizard navigation based on domain verification, adapt useConfigureSSO mock shape to enterpriseConnectionMutations, and verify domain-forwarding into createConnection.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • wobsoriano
  • mauricioabreu

"🐰
I twitch my nose and dig the ground,
TXT records hum without a sound.
Domains hop in, proof tucked tight,
SSO leaps forward into light."

@pkg-pr-new

pkg-pr-new Bot commented Jun 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8788

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8788

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8788

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8788

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8788

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8788

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8788

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8788

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8788

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8788

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8788

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8788

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8788

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8788

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8788

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8788

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8788

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8788

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8788

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8788

commit: fe8b686

@LauraBeatris LauraBeatris force-pushed the laura/orgs-1594-sdk-add-txt-domain-verification-step branch from 094e372 to 33542f0 Compare June 9, 2026 20:38
@LauraBeatris LauraBeatris force-pushed the laura/orgs-1594-sdk-add-txt-domain-verification-step branch from 32ca101 to 38dd43d Compare June 10, 2026 18:21
@LauraBeatris LauraBeatris force-pushed the laura/orgs-1594-sdk-add-txt-domain-verification-step branch from 38dd43d to 222c676 Compare June 10, 2026 18:24
@LauraBeatris LauraBeatris force-pushed the laura/orgs-1594-sdk-add-txt-domain-verification-step branch from 856c3fe to daa721d Compare June 10, 2026 19:10
@LauraBeatris LauraBeatris force-pushed the laura/orgs-1594-sdk-add-txt-domain-verification-step branch from 6505c89 to b986eaa Compare June 10, 2026 19:33
@LauraBeatris LauraBeatris force-pushed the laura/orgs-1594-sdk-add-txt-domain-verification-step branch from a638361 to d6c8f02 Compare June 12, 2026 19:33
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-12T21:06:33.644Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 2
🔴 Breaking changes 0
🟡 Non-breaking changes 5
🟢 Additions 39

🤖 This report was reviewed by claude-sonnet-4-6.

Note
Break Check could not snapshot 1 subpath; the diff below excludes them.

  • @clerk/astro ./env: ambient declaration file (no top-level import or export): API Extractor can only analyze module entry points, so this global-augmentation surface cannot be snapshotted; add the subpath to ignoreSubpaths to acknowledge it (API Extractor: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts)

@clerk/shared

Current version: 4.17.1
Recommended bump: MINOR → 4.18.0

Subpath ./react

🟢 Additions (2)

Added: UseOrganizationDomainsParams
+ type UseOrganizationDomainsParams = {
+   enabled?: boolean;
+   keepPreviousData?: boolean;
+   enrollmentMode?: OrganizationEnrollmentMode;
+ };

Added type alias UseOrganizationDomainsParams

Added: UseOrganizationDomainsReturn
+ type UseOrganizationDomainsReturn = {
+   data: OrganizationDomainResource[] | undefined;
+   totalCount: number | undefined;
+   error: Error | null;
+   isLoading: boolean;
+   isFetching: boolean;
+   createDomain: (name: string) => Promise<OrganizationDomainResource | undefined>;
+   prepareOwnershipVerification: (domains: OrganizationDomainResource[]) => Promise<OrganizationDomainsBulkOwnershipVerificationResource | undefined>;
+   attemptOwnershipVerification: (domains: OrganizationDomainResource[]) => Promise<OrganizationDomainsBulkOwnershipVerificationResource | undefined>;
+   revalidate: () => Promise<void>;
+ };

Added type alias UseOrganizationDomainsReturn

Subpath ./types

🟡 Non-breaking Changes (4)

Modified: __internal_LocalizationResource
Diff (before: 1924 lines, after: 1923 lines). Click to expand.
// ... 945 unchanged lines elided ...
      badge__automaticInvitation: LocalizationValue;
      badge__automaticSuggestion: LocalizationValue;
      badge__manualInvitation: LocalizationValue;
+     badge__enterpriseSso: LocalizationValue;
      start: {
        headerTitle__members: LocalizationValue;
        membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
        headerTitle__general: LocalizationValue;
        profileSection: {
          title: LocalizationValue;
          primaryButton: LocalizationValue;
          uploadAction__title: LocalizationValue;
        };
      };
      profilePage: {
        title: LocalizationValue;
        successMessage: LocalizationValue;
        dangerSection: {
          title: LocalizationValue;
          leaveOrganization: {
            title: LocalizationValue;
            messageLine1: LocalizationValue;
            messageLine2: LocalizationValue;
            successMessage: LocalizationValue;
            actionDescription: LocalizationValue<'organizationName'>;
          };
          deleteOrganization: {
            title: LocalizationValue;
            messageLine1: LocalizationValue;
            messageLine2: LocalizationValue;
            actionDescription: LocalizationValue<'organizationName'>;
            successMessage: LocalizationValue;
          };
        };
        domainSection: {
          title: LocalizationValue;
          subtitle: LocalizationValue;
          primaryButton: LocalizationValue;
          menuAction__verify: LocalizationValue;
          menuAction__remove: LocalizationValue;
          menuAction__manage: LocalizationValue;
        };
      };
      createDomainPage: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
      };
      verifyDomainPage: {
        title: LocalizationValue;
        subtitle: LocalizationValue<'domainName'>;
        subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
        formTitle: LocalizationValue;
        formSubtitle: LocalizationValue;
        resendButton: LocalizationValue;
      };
      verifiedDomainPage: {
        title: LocalizationValue<'domain'>;
        subtitle: LocalizationValue<'domain'>;
        start: {
          headerTitle__enrollment: LocalizationValue;
          headerTitle__danger: LocalizationValue;
        };
        enrollmentTab: {
          subtitle: LocalizationValue;
          manualInvitationOption__label: LocalizationValue;
          manualInvitationOption__description: LocalizationValue;
          automaticInvitationOption__label: LocalizationValue;
          automaticInvitationOption__description: LocalizationValue;
          automaticSuggestionOption__label: LocalizationValue;
          automaticSuggestionOption__description: LocalizationValue;
          calloutInfoLabel: LocalizationValue;
          calloutInvitationCountLabel: LocalizationValue<'count'>;
          calloutSuggestionCountLabel: LocalizationValue<'count'>;
        };
        dangerTab: {
          removeDomainTitle: LocalizationValue;
          removeDomainSubtitle: LocalizationValue;
          removeDomainActionLabel__remove: LocalizationValue;
          calloutInfoLabel: LocalizationValue;
        };
      };
      invitePage: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        successMessage: LocalizationValue;
        detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
        formButtonPrimary__continue: LocalizationValue;
        formButtonPrimary__purchaseSeats: LocalizationValue;
        selectDropdown__role: LocalizationValue;
      };
      removeDomainPage: {
        title: LocalizationValue;
        messageLine1: LocalizationValue<'domain'>;
        messageLine2: LocalizationValue;
        successMessage: LocalizationValue;
      };
      membersPage: {
        detailsTitle__emptyRow: LocalizationValue;
        action__invite: LocalizationValue;
        action__search: LocalizationValue;
        start: {
          headerTitle__members: LocalizationValue;
          headerTitle__invitations: LocalizationValue;
          headerTitle__requests: LocalizationValue;
        };
        activeMembersTab: {
          tableHeader__user: LocalizationValue;
          tableHeader__joined: LocalizationValue;
          tableHeader__role: LocalizationValue;
          tableHeader__actions: LocalizationValue;
          menuAction__remove: LocalizationValue;
        };
        invitedMembersTab: {
          tableHeader__invited: LocalizationValue;
          menuAction__revoke: LocalizationValue;
        };
        invitationsTab: {
          table__emptyRow: LocalizationValue;
          autoInvitations: {
            headerTitle: LocalizationValue;
            headerSubtitle: LocalizationValue;
            primaryButton: LocalizationValue;
          };
        };
        requestsTab: {
          tableHeader__requested: LocalizationValue;
          menuAction__approve: LocalizationValue;
          menuAction__reject: LocalizationValue;
          table__emptyRow: LocalizationValue;
          autoSuggestions: {
            headerTitle: LocalizationValue;
            headerSubtitle: LocalizationValue;
            primaryButton: LocalizationValue;
          };
        };
        alerts: {
          roleSetMigrationInProgress: {
            title: LocalizationValue;
            subtitle: LocalizationValue;
          };
        };
      };
      billingPage: {
        title: LocalizationValue;
        start: {
          headerTitle__payments: LocalizationValue;
          headerTitle__plans: LocalizationValue;
          headerTitle__subscriptions: LocalizationValue;
          headerTitle__statements: LocalizationValue;
        };
        statementsSection: {
          empty: LocalizationValue;
          itemCaption__paidForPlan: LocalizationValue<'plan' | 'period'>;
          itemCaption__proratedCredit: LocalizationValue;
          itemCaption__payerCredit: LocalizationValue;
          itemCaption__subscribedAndPaidForPlan: LocalizationValue<'plan' | 'period'>;
          notFound: LocalizationValue;
          tableHeader__date: LocalizationValue;
          tableHeader__amount: LocalizationValue;
          title: LocalizationValue;
          totalPaid: LocalizationValue;
        };
        switchPlansSection: {
          title: LocalizationValue;
        };
        subscriptionsListSection: {
          tableHeader__plan: LocalizationValue;
          tableHeader__startDate: LocalizationValue;
          tableHeader__edit: LocalizationValue;
          title: LocalizationValue;
          actionLabel__newSubscription: LocalizationValue;
          actionLabel__manageSubscription: LocalizationValue;
          actionLabel__switchPlan: LocalizationValue;
          includedSeatsUsage: LocalizationValue<'includedSeats'>;
          overview: LocalizationValue;
          paidSeatsUsage: LocalizationValue<'seatsQuantity' | 'amount'>;
          seatLimit: LocalizationValue<'seatLimit'>;
          seatLimitAndIncludedSeats: LocalizationValue<'seatLimit' | 'includedSeats'>;
        };
        paymentHistorySection: {
          empty: LocalizationValue;
          notFound: LocalizationValue;
          tableHeader__date: LocalizationValue;
          tableHeader__amount: LocalizationValue;
          tableHeader__status: LocalizationValue;
        };
        paymentMethodsSection: {
          title: LocalizationValue;
          add: LocalizationValue;
          addSubtitle: LocalizationValue;
          cancelButton: LocalizationValue;
          actionLabel__default: LocalizationValue;
          actionLabel__remove: LocalizationValue;
          formButtonPrimary__add: LocalizationValue;
          formButtonPrimary__pay: LocalizationValue;
          removeMethod: {
            title: LocalizationValue;
            messageLine1: LocalizationValue<'identifier'>;
            messageLine2: LocalizationValue;
            successMessage: LocalizationValue<'paymentMethod'>;
          };
          payWithTestCardButton: LocalizationValue;
        };
        subscriptionsSection: {
          actionLabel__default: LocalizationValue;
        };
      };
      plansPage: {
        title: LocalizationValue;
        alerts: {
          noPermissionsToManageBilling: LocalizationValue;
          planMembershipLimitExceeded: LocalizationValue<'count' | 'limit'>;
        };
      };
      apiKeysPage: {
        title: LocalizationValue;
        detailsTitle__emptyRow: LocalizationValue;
      };
    };
    createOrganization: {
      title: LocalizationValue;
      formButtonSubmit: LocalizationValue;
      invitePage: {
        formButtonReset: LocalizationValue;
      };
    };
    organizationList: {
      createOrganization: LocalizationValue;
      title: LocalizationValue<'applicationName'>;
      titleWithoutPersonal: LocalizationValue;
      subtitle: LocalizationValue<'applicationName'>;
      action__invitationAccept: LocalizationValue;
      invitationAcceptedLabel: LocalizationValue;
      action__suggestionsAccept: LocalizationValue;
      suggestionsAcceptedLabel: LocalizationValue;
      action__createOrganization: LocalizationValue;
    };
    oauthConsent: {
      subtitle: LocalizationValue<'applicationName' | 'identifier'>;
      scopeList: {
        title: LocalizationValue<'applicationName'>;
      };
      action__deny: LocalizationValue;
      action__allow: LocalizationValue;
      warning: LocalizationValue<'applicationName' | 'domainAction'>;
      redirectNotice: LocalizationValue<'domainAction'>;
      offlineAccessNotice: LocalizationValue;
      viewFullUrl: LocalizationValue;
      redirectUriModal: {
        title: LocalizationValue;
        subtitle: LocalizationValue<'applicationName'>;
      };
    };
    unstable__errors: UnstableErrors;
    dates: {
      previous6Days: LocalizationValue<'date'>;
      lastDay: LocalizationValue<'date'>;
      sameDay: LocalizationValue<'date'>;
      nextDay: LocalizationValue<'date'>;
      next6Days: LocalizationValue<'date'>;
      numeric: LocalizationValue<'date'>;
    };
    waitlist: {
      start: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        formButton: LocalizationValue;
        actionText: LocalizationValue;
        actionLink: LocalizationValue;
      };
      success: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        message: LocalizationValue;
      };
    };
    configureSSO: {
      missingManageEnterpriseConnectionsPermission: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
      };
      navbar: {
        title: LocalizationValue;
      };
      resetConnectionDialog: {
        cancelButton: LocalizationValue;
        confirmationFieldLabel: LocalizationValue<'name'>;
        confirmationFieldPlaceholder: LocalizationValue<'name'>;
        resetButton: LocalizationValue;
        subtitle: LocalizationValue;
        title: LocalizationValue;
      };
      selectProviderStep: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        saml: {
          groupLabel: LocalizationValue;
          okta: LocalizationValue;
          customSaml: LocalizationValue;
          google: LocalizationValue;
          microsoft: LocalizationValue;
        };
        warning: LocalizationValue;
      };
-     verifyEmailDomainStep: {
+     organizationDomainsStep: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
-       addEmailAddress: {
-         formTitle: LocalizationValue;
-         formSubtitle: LocalizationValue;
-         inputPlaceholder: LocalizationValue;
-         inputLabel: LocalizationValue;
+       formFieldLabel__domain: LocalizationValue;
+       formFieldInputPlaceholder__domain: LocalizationValue;
+       formButtonPrimary__add: LocalizationValue;
+       domainSuggestion: {
+         messageLabel: LocalizationValue<'domain'>;
+         formButtonPrimary__add: LocalizationValue<'domain'>;
        };
-       emailCode: {
-         formTitle: LocalizationValue;
-         formSubtitle: LocalizationValue<'identifier'>;
-         resendButton: LocalizationValue;
-         verified: {
-           title: LocalizationValue;
-           subtitle: LocalizationValue;
-           inputLabel: LocalizationValue;
+       domainCard: {
+         badge__verified: LocalizationValue;
+         badge__unverified: LocalizationValue;
+         verifiedAtLabel: LocalizationValue<'date'>;
+         txtRecord: {
+           instructions: LocalizationValue;
+           typeLabel: LocalizationValue;
+           hostLabel: LocalizationValue;
+           valueLabel: LocalizationValue;
          };
-       };
-       domainTaken: {
-         title: LocalizationValue<'domain'>;
-         subtitle: LocalizationValue;
        };
      };
      testConfigurationStep: {
// ... 651 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (70%): The before/after snippets differ by only 1 line (1844 vs 1843 elided lines), suggesting a single field was removed or renamed; however, __internal_LocalizationResource is used only as the base for DeepPartial<DeepLocalizationWithoutObjects<...>> which feeds into LocalizationResource (an output/read type), and the __internal_ prefix marks it as internal. Without seeing the exact differing field, the consumer-facing impact via the DeepPartial wrapper is minimal for well-typed consumers who only provide partial overrides, but the uncertainty warrants a lower confidence.

Modified: FieldId
- type FieldId = 'firstName' | 'lastName' | 'name' | 'slug' | 'emailAddress' | 'phoneNumber' | 'currentPassword' | 'newPassword' | 'signOutOfOtherSessions' | 'passkeyName' | 'password' | 'confirmPassword' | 'identifier' | 'username' | 'code' | 'role' | 'deleteConfirmation' | 'deleteOrganizationConfirmation' | 'enrollmentMode' | 'affiliationEmailAddress' | 'deleteExistingInvitationsSuggestions' | 'legalAccepted' | 'apiKeyDescription' | 'apiKeyExpirationDate' | 'apiKeyRevokeConfirmation' | 'apiKeySecret' | 'idpCertificate' | 'idpEntityId' | 'idpMetadata' | 'idpMetadataUrl' | 'idpSsoUrl' | 'acsUrl' | 'spEntityId' | 'web3WalletName';
+ type FieldId = 'firstName' | 'lastName' | 'name' | 'slug' | 'emailAddress' | 'phoneNumber' | 'currentPassword' | 'newPassword' | 'signOutOfOtherSessions' | 'passkeyName' | 'password' | 'confirmPassword' | 'identifier' | 'username' | 'code' | 'role' | 'deleteConfirmation' | 'deleteOrganizationConfirmation' | 'enrollmentMode' | 'affiliationEmailAddress' | 'deleteExistingInvitationsSuggestions' | 'legalAccepted' | 'apiKeyDescription' | 'apiKeyExpirationDate' | 'apiKeyRevokeConfirmation' | 'apiKeySecret' | 'idpCertificate' | 'idpEntityId' | 'idpMetadata' | 'idpMetadataUrl' | 'idpSsoUrl' | 'acsUrl' | 'spEntityId' | 'web3WalletName' | 'domain';

Static analyzer: Breaking change in type alias FieldId: Type changed: 'acsUrl'|'affiliationEmailAddress'|'apiKeyDescription'|'apiKeyExpirationDate'|'apiKeyRevokeConfirmation'|'apiKeySecret'…'acsUrl'|'affiliationEmailAddress'|'apiKeyDescription'|'apiKeyExpirationDate'|'apiKeyRevokeConfirmation'|'apiKeySecret'…

🤖 AI review (reclassified as non-breaking) (90%): A new string literal member 'domain' was added to the FieldId union; existing consumers who exhaustively switch over FieldId or assign a FieldId-typed variable would not break (their values remain valid members), but code doing exhaustive checks may miss the new variant at runtime. As a union expansion (addition of a new member), this is non-breaking for well-typed consumers in input positions and is effectively an addition.

Modified: OrganizationEnrollmentMode
- type OrganizationEnrollmentMode = 'manual_invitation' | 'automatic_invitation' | 'automatic_suggestion';
+ type OrganizationEnrollmentMode = 'manual_invitation' | 'automatic_invitation' | 'automatic_suggestion' | 'enterprise_sso';

Static analyzer: Breaking change in type alias OrganizationEnrollmentMode: Type changed: 'automatic_invitation'|'automatic_suggestion'|'manual_invitation''automatic_invitation'|'automatic_suggestion'|'enterprise_sso'|'manual_invitation'

🤖 AI review (reclassified as non-breaking) (75%): Adding 'enterprise_sso' to the OrganizationEnrollmentMode union widens the type; in input positions (e.g., CreateOrganizationDomainParams.enrollmentMode, GetDomainsParams.enrollmentMode) existing callers pass valid existing values which are still accepted, and in output positions (e.g., OrganizationDomainResource.enrollmentMode) consumers reading the value may encounter the new variant but well-typed code handling the union exhaustively could miss it—however, union widening is generally non-breaking for input usage, and for output usage it only requires consumers to handle a new case, not adapt existing code to compile.

Modified: OrganizationResource.createDomain
- createDomain: (domainName: string) => Promise<OrganizationDomainResource>;
+ createDomain: (domainName: string, params?: Pick<CreateOrganizationDomainParams, 'enrollmentMode'>) => Promise<OrganizationDomainResource>;

Static analyzer: Breaking change in property OrganizationResource.createDomain: Type changed: (domainName:string)=>!Promise:interface<import("@clerk/shared").OrganizationDomainResource>(domainName:string,params?:!Pick:type<import("@clerk/shared").CreateOrganizationDomainParams,'enrollmentMode'>)=>!Promi…

🤖 AI review (reclassified as non-breaking) (95%): An optional second parameter params?: Pick<CreateOrganizationDomainParams, 'enrollmentMode'> was added to OrganizationResource.createDomain; existing call sites passing only domainName remain valid since the new parameter is optional.

🟢 Additions (37)

Click to expand 37 changes
Added: CreateOrganizationDomainParams
+ type CreateOrganizationDomainParams = {
+   name: string;
+   enrollmentMode?: OrganizationEnrollmentMode;
+ };

Added type alias CreateOrganizationDomainParams

Added: OrganizationDomainBulkOwnershipVerificationError
+ interface OrganizationDomainBulkOwnershipVerificationError

Added interface OrganizationDomainBulkOwnershipVerificationError

Added: OrganizationDomainBulkOwnershipVerificationError.code
+ code: string;

Added property OrganizationDomainBulkOwnershipVerificationError.code

Added: OrganizationDomainBulkOwnershipVerificationError.id
+ id: string;

Added property OrganizationDomainBulkOwnershipVerificationError.id

Added: OrganizationDomainBulkOwnershipVerificationErrorJSON
+ interface OrganizationDomainBulkOwnershipVerificationErrorJSON

Added interface OrganizationDomainBulkOwnershipVerificationErrorJSON

Added: OrganizationDomainBulkOwnershipVerificationErrorJSON.code
+ code: string;

Added property OrganizationDomainBulkOwnershipVerificationErrorJSON.code

Added: OrganizationDomainBulkOwnershipVerificationErrorJSON.id
+ id: string;

Added property OrganizationDomainBulkOwnershipVerificationErrorJSON.id

Added: OrganizationDomainJSON.affiliation_verification
+ affiliation_verification: OrganizationDomainVerificationJSON | null;

Added property OrganizationDomainJSON.affiliation_verification

Added: OrganizationDomainJSON.ownership_verification
+ ownership_verification: OrganizationDomainOwnershipVerificationJSON | null;

Added property OrganizationDomainJSON.ownership_verification

Added: OrganizationDomainOwnershipVerification
+ interface OrganizationDomainOwnershipVerification

Added interface OrganizationDomainOwnershipVerification

Added: OrganizationDomainOwnershipVerification.attempts
+ attempts: number | null;

Added property OrganizationDomainOwnershipVerification.attempts

Added: OrganizationDomainOwnershipVerification.expiresAt
+ expiresAt: Date | null;

Added property OrganizationDomainOwnershipVerification.expiresAt

Added: OrganizationDomainOwnershipVerification.status
+ status: OrganizationDomainVerificationStatus;

Added property OrganizationDomainOwnershipVerification.status

Added: OrganizationDomainOwnershipVerification.strategy
+ strategy: OrganizationDomainOwnershipVerificationStrategy;

Added property OrganizationDomainOwnershipVerification.strategy

Added: OrganizationDomainOwnershipVerification.txtRecordName
+ txtRecordName: string | null;

Added property OrganizationDomainOwnershipVerification.txtRecordName

Added: OrganizationDomainOwnershipVerification.txtRecordValue
+ txtRecordValue: string | null;

Added property OrganizationDomainOwnershipVerification.txtRecordValue

Added: OrganizationDomainOwnershipVerification.verifiedAt
+ verifiedAt: Date | null;

Added property OrganizationDomainOwnershipVerification.verifiedAt

Added: OrganizationDomainOwnershipVerificationJSON
+ interface OrganizationDomainOwnershipVerificationJSON

Added interface OrganizationDomainOwnershipVerificationJSON

Added: OrganizationDomainOwnershipVerificationJSON.attempts
+ attempts: number | null;

Added property OrganizationDomainOwnershipVerificationJSON.attempts

Added: OrganizationDomainOwnershipVerificationJSON.expire_at
+ expire_at: number | null;

Added property OrganizationDomainOwnershipVerificationJSON.expire_at

Added: OrganizationDomainOwnershipVerificationJSON.status
+ status: OrganizationDomainVerificationStatus;

Added property OrganizationDomainOwnershipVerificationJSON.status

Added: OrganizationDomainOwnershipVerificationJSON.strategy
+ strategy: OrganizationDomainOwnershipVerificationStrategy;

Added property OrganizationDomainOwnershipVerificationJSON.strategy

Added: OrganizationDomainOwnershipVerificationJSON.txt_record_name
+ txt_record_name?: string | null;

Added property OrganizationDomainOwnershipVerificationJSON.txt_record_name

Added: OrganizationDomainOwnershipVerificationJSON.txt_record_value
+ txt_record_value?: string | null;

Added property OrganizationDomainOwnershipVerificationJSON.txt_record_value

Added: OrganizationDomainOwnershipVerificationJSON.verified_at
+ verified_at: number | null;

Added property OrganizationDomainOwnershipVerificationJSON.verified_at

Added: OrganizationDomainOwnershipVerificationStrategy
+ type OrganizationDomainOwnershipVerificationStrategy = 'txt' | 'legacy' | 'manual_override';

Added type alias OrganizationDomainOwnershipVerificationStrategy

Added: OrganizationDomainResource.affiliationVerification
+ affiliationVerification: OrganizationDomainVerification | null;

Added property OrganizationDomainResource.affiliationVerification

Added: OrganizationDomainResource.ownershipVerification
+ ownershipVerification: OrganizationDomainOwnershipVerification | null;

Added property OrganizationDomainResource.ownershipVerification

Added: OrganizationDomainsBulkOwnershipVerificationJSON
+ interface OrganizationDomainsBulkOwnershipVerificationJSON

Added interface OrganizationDomainsBulkOwnershipVerificationJSON

Added: OrganizationDomainsBulkOwnershipVerificationJSON.data
+ data: OrganizationDomainJSON[];

Added property OrganizationDomainsBulkOwnershipVerificationJSON.data

Added: OrganizationDomainsBulkOwnershipVerificationJSON.errors
+ errors: OrganizationDomainBulkOwnershipVerificationErrorJSON[];

Added property OrganizationDomainsBulkOwnershipVerificationJSON.errors

Added: OrganizationDomainsBulkOwnershipVerificationResource
+ interface OrganizationDomainsBulkOwnershipVerificationResource

Added interface OrganizationDomainsBulkOwnershipVerificationResource

Added: OrganizationDomainsBulkOwnershipVerificationResource.data
+ data: OrganizationDomainResource[];

Added property OrganizationDomainsBulkOwnershipVerificationResource.data

Added: OrganizationDomainsBulkOwnershipVerificationResource.errors
+ errors: OrganizationDomainBulkOwnershipVerificationError[];

Added property OrganizationDomainsBulkOwnershipVerificationResource.errors

Added: OrganizationDomainVerificationJSON.verified_at
+ verified_at?: number | null;

Added property OrganizationDomainVerificationJSON.verified_at

Added: OrganizationResource.attemptOwnershipVerification
+ attemptOwnershipVerification: (domainIds: string[]) => Promise<OrganizationDomainsBulkOwnershipVerificationResource>;

Added property OrganizationResource.attemptOwnershipVerification

Added: OrganizationResource.prepareOwnershipVerification
+ prepareOwnershipVerification: (domainIds: string[]) => Promise<OrganizationDomainsBulkOwnershipVerificationResource>;

Added property OrganizationResource.prepareOwnershipVerification


@clerk/ui

Current version: 1.16.1
Recommended bump: MINOR → 1.17.0

Subpath ./internal

🟡 Non-breaking Changes (1)

Modified: ElementsConfig
// ... 478 unchanged lines elided ...
    configureSSOVerifyDomainErrorIcon: WithOptions;
    configureSSOVerifyDomainErrorTitle: WithOptions;
    configureSSOVerifyDomainErrorSubtitle: WithOptions;
+   configureSSOVerifyDomainList: WithOptions;
+   configureSSOVerifyDomainSuggestion: WithOptions;
+   configureSSOVerifyDomainCard: WithOptions<'verified' | 'unverified'>;
+   configureSSOVerifyDomainCardBadge: WithOptions<'verified' | 'unverified'>;
+   configureSSOVerifyDomainCardRemoveButton: WithOptions;
+   configureSSOVerifyDomainCardTxtRecord: WithOptions;
+   configureSSOVerifyDomainCardTxtRecordValue: WithOptions;
    configureSSOEmailVerificationForm: WithOptions<string>;
    configureSSOEmailVerificationIcon: WithOptions<string>;
    configureSSOEmailVerificationTitle: WithOptions<string>;
// ... 47 unchanged lines elided ...

Static analyzer: Breaking change in type alias ElementsConfig: Type changed: {button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…{button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…

🤖 AI review (reclassified as non-breaking) (85%): The change adds new properties to ElementsConfig (7 more lines elided in the after-snippet), and ElementsConfig is used only as the key source for the Elements mapped type (an output/read type alias) — consumers never construct values of ElementsConfig directly, so adding new keys is non-breaking.


Report generated by Break Check

Last ran on fe8b686.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts (1)

159-202: ⚠️ Potential issue | 🟡 Minor

Add user to enterpriseConnectionMutations useMemo dependencies

  • createConnection reads user?.primaryEmailAddress, but user is not in the useMemo dependency array, so connectionName can be derived from a stale/undefined user.
🔧 Proposed fix
-  }, [organization, createEnterpriseConnection, updateEnterpriseConnection, deleteEnterpriseConnection]);
+  }, [user, organization, createEnterpriseConnection, updateEnterpriseConnection, deleteEnterpriseConnection]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts`
around lines 159 - 202, The useMemo creating enterpriseConnectionMutations
closes over user (read in createConnection via user?.primaryEmailAddress) but
user is missing from the dependency array, risking stale values; update the
useMemo dependencies for enterpriseConnectionMutations to include user (so that
createConnection recomputes when user or their primaryEmailAddress changes) —
i.e., add user (or user.primaryEmailAddress if you prefer finer-grained deps) to
the array currently containing [organization, createEnterpriseConnection,
updateEnterpriseConnection, deleteEnterpriseConnection].
🧹 Nitpick comments (3)
packages/clerk-js/src/core/resources/Organization.ts (1)

294-334: ⚡ Quick win

Add JSDoc for the new public organization-domain methods before merge.

createDomain, prepareOwnershipVerification, and attemptOwnershipVerification expand the public Organization API but currently ship without JSDoc. In this repo, these comments can flow into generated reference docs, so this creates docs drift risk. Please add concise @param/@returns (including bulk errors semantics) and flag for Docs review.

As per coding guidelines, packages/**/src/**/*.{ts,tsx} requires comprehensive JSDoc for public APIs, and public/reference-facing JSDoc changes may need Docs-team review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/core/resources/Organization.ts` around lines 294 - 334,
Add JSDoc comments to the public Organization methods createDomain,
prepareOwnershipVerification, and attemptOwnershipVerification (and update
bulkOwnershipVerification's visibility note if needed) describing purpose,
parameters, return types, and errors semantics; include `@param` for name and
params/enrollmentMode on createDomain and `@param` for domainIds on the two bulk
methods, an `@returns` that documents the Promise shape including the data array
and bulk errors array, and a short `@remarks` or `@public` note to flag that this is
reference-facing and requires Docs-team review.

Source: Coding guidelines

packages/ui/src/components/ConfigureSSO/steps/index.ts (1)

3-3: ⚡ Quick win

Avoid expanding the index.ts barrel surface.

Line 3 adds another re-export from an index.ts barrel. Prefer direct imports from ./OrganizationDomainsStep at call sites to avoid increasing circular-dependency risk in this module graph.

As per coding guidelines, **/index.ts: “Avoid barrel files (index.ts re-exports) as they can cause circular dependencies.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ConfigureSSO/steps/index.ts` at line 3, Remove the
new re-export from the ConfigureSSO steps barrel: delete the line exporting
OrganizationDomainsStep from this index.ts and update any call sites that
currently import OrganizationDomainsStep from the barrel to import directly from
'./OrganizationDomainsStep'; this keeps the symbol OrganizationDomainsStep
defined only in its module and avoids expanding the index.ts surface that can
introduce circular-dependency risk.

Source: Coding guidelines

packages/localizations/src/zh-CN.ts (1)

199-205: Confirm organizationDomainsStep partials are OK: missing domainSuggestion/domainCard fall back to en-US

packages/localizations/src/zh-CN.ts (lines 199-205) only provides a subset of configureSSO.organizationDomainsStep, which is fine because LocalizationResource is a DeepPartial (nested keys are optional) and @clerk/ui deep-merges the user locale over the default en-US resource—so omitted domainSuggestion / domainCard values will come from enUS instead of becoming undefined.

  • Optional: add the missing domainSuggestion and domainCard translations for more complete zh-CN coverage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/localizations/src/zh-CN.ts` around lines 199 - 205, The zh-CN
localization for configureSSO only includes a subset for organizationDomainsStep
(title, subtitle, formFieldLabel__domain, formFieldInputPlaceholder__domain,
formButtonPrimary__add), so domainSuggestion and domainCard are missing and will
fall back to en-US; to fix, add the missing keys domainSuggestion and domainCard
under organizationDomainsStep in the same object (provide their Chinese strings)
so zh-CN fully covers configureSSO.organizationDomainsStep and avoids relying on
the enUS fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/localizations/src/ar-SA.ts`:
- Around line 199-205: The localization object organizationDomainsStep is
missing the new nested keys required for TXT verification; add explicit
undefined placeholders for domainSuggestion (and its expected children if any),
domainCard, and domainCard.txtRecord inside organizationDomainsStep in each of
the three locale files (ar-SA.ts, be-BY.ts, bg-BG.ts) so the shape stays
consistent and fallback works; specifically update the organizationDomainsStep
entry to include domainSuggestion: undefined, domainCard: { txtRecord: undefined
} (and any other new nested keys) rather than omitting them.

In `@packages/localizations/src/uk-UA.ts`:
- Line 525: Change the translation entry that currently reads title: 'Verify
domains' to use the singular form for the single-domain verification screen;
locate the translation object entry containing the key/value pair title: 'Verify
domains' in packages/localizations/src/uk-UA.ts and update the string to 'Verify
domain' (or the appropriate singular Ukrainian equivalent) so the title matches
the per-domain verification subtitle.

In `@packages/localizations/src/utils/enUS_v4.ts`:
- Around line 595-597: The title for verifyDomainPage is plural ("Verify
domains") while subtitle and subtitleVerificationCodeScreen refer to a single
domain; update verifyDomainPage.title to the singular form "Verify domain" (or
alternatively make subtitle/subtitleVerificationCodeScreen plural if the flow
supports multiple domains) so the copy is consistent across
verifyDomainPage.title, verifyDomainPage.subtitle, and
verifyDomainPage.subtitleVerificationCodeScreen.

In `@packages/shared/src/react/hooks/useOrganizationDomains.tsx`:
- Around line 94-123: The mutation helpers createDomain,
prepareOwnershipVerification, and attemptOwnershipVerification currently only
call revalidate on the success path; move the revalidate call into a finally
block (or ensure it always runs on both success and error) so cache invalidation
happens even if ownership preparation/attempt throws; for createDomain
specifically, perform the create call, then try the prepareOwnershipVerification
step, and in a finally call revalidate before rethrowing any caught error
(preserving the returned/created value on success), and do the same pattern for
prepareOwnershipVerification and attemptOwnershipVerification so revalidate
always runs regardless of errors.

In `@packages/shared/src/types/organization.ts`:
- Around line 192-201: The `@returns` links for the bulk domain ownership methods
incorrectly point to the single-domain resource URL
(organization-domain-resource) while both methods return
OrganizationDomainsBulkOwnershipVerificationResource; update the two `@returns`
link targets to reference the correct bulk resource (replace the existing
https://clerk.com/docs/reference/types/organization-domain-resource link with
the correct URL for OrganizationDomainsBulkOwnershipVerificationResource) for
the prepareOwnershipVerification method and the bulk TXT-resolution method (the
one that "Resolves the published TXT record...") so the generated reference docs
match the actual return type.

In `@packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx`:
- Around line 26-28: The missing-permission org test stalls because ConfigureSSO
calls __internal_useOrganizationDomains but the test (the "shows a warning if
the active organization membership lacks the manage enterprise connections
permission" case using f.withOrganizations()) only stubs
getEnterpriseConnections; fix it by calling the existing mock helper
mockOrganizationDomains(fixtures, []) (or with a non-empty domains array) in
that test before rendering so fixtures.clerk.organization.getDomains is mocked
and resolves.

In
`@packages/ui/src/components/ConfigureSSO/hooks/__tests__/useOrganizationEnterpriseConnection.test.tsx`:
- Around line 192-194: The test is using .resolves on a call that returns
undefined (not a Promise); change the assertion to assert the synchronous return
instead of using .resolves: replace await
expect(result.current.enterpriseConnectionMutations.createConnection('saml_okta',
undefined)).resolves.toBeUndefined() with a direct synchronous assertion like
expect(result.current.enterpriseConnectionMutations.createConnection('saml_okta',
undefined)).toBeUndefined() (or, if you prefer to keep a Promise-based check,
wrap the call with Promise.resolve(...) and use .resolves.toBeUndefined());
reference enterpriseConnectionMutations.createConnection in the test.

In `@packages/ui/src/components/ConfigureSSO/steps/OrganizationDomainsStep.tsx`:
- Around line 324-327: The Text span in OrganizationDomainsStep (the Text
element rendered with sx={t => ({ fontWeight: t.fontWeights.$medium, overflow:
'hidden', textOverflow: 'ellipsis' })}) lacks white-space handling so the
ellipsis can fail; update its sx style to include whiteSpace: 'nowrap' alongside
overflow: 'hidden' and textOverflow: 'ellipsis' to ensure the ellipsis renders
correctly for this non-input text node.
- Around line 110-113: The onRemove handler currently calls
domain.delete().then(() => revalidate()) and swallows any errors; update the
onRemove implementation to await domain.delete() and revalidate() inside a
try/catch (or handle .catch) so failures are caught, and surface errors to the
user (e.g., show a toast/error banner or set an error state) and optionally log
the error; ensure you still call revalidate() only on success and disable any
optimistic UI changes until the operation succeeds. Reference: onRemove handler,
domain.delete(), revalidate().

In `@packages/ui/src/components/OrganizationProfile/EnrollmentBadge.tsx`:
- Line 10: The enterprise_sso badge was added (enterprise_sso) but the render
gating still checks organizationDomain.verification.status, which misses
TXT-owned domains verified via ownershipVerification; update the gating logic in
EnrollmentBadge (where enterprise_sso is used) to check
ownershipVerification.status === 'verified' (or fallback to
organizationDomain.verification.status if needed) so the badge shows for
TXT-verified domains; modify the conditional that currently references
organizationDomain.verification.status to include ownershipVerification.status
as the authoritative check for enterprise SSO.

In `@packages/ui/src/internal/appearance.ts`:
- Around line 707-713: The appearance key
configureSSOVerifyDomainCardTxtRecordValue is declared and registered but never
applied to the rendered TXT value; either wire it into the rendered element or
remove the key and its registration. Fix by updating the RecordEntry component
where the TXT value is rendered (the Badge or the element rendering the TXT
string) to use
elementDescriptor={descriptors.configureSSOVerifyDomainCardTxtRecordValue} so
the themed element is applied, or if theming that value is unnecessary, remove
configureSSOVerifyDomainCardTxtRecordValue from the appearance contract and from
elementDescriptors to keep the contract and descriptors in sync.

---

Outside diff comments:
In
`@packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts`:
- Around line 159-202: The useMemo creating enterpriseConnectionMutations closes
over user (read in createConnection via user?.primaryEmailAddress) but user is
missing from the dependency array, risking stale values; update the useMemo
dependencies for enterpriseConnectionMutations to include user (so that
createConnection recomputes when user or their primaryEmailAddress changes) —
i.e., add user (or user.primaryEmailAddress if you prefer finer-grained deps) to
the array currently containing [organization, createEnterpriseConnection,
updateEnterpriseConnection, deleteEnterpriseConnection].

---

Nitpick comments:
In `@packages/clerk-js/src/core/resources/Organization.ts`:
- Around line 294-334: Add JSDoc comments to the public Organization methods
createDomain, prepareOwnershipVerification, and attemptOwnershipVerification
(and update bulkOwnershipVerification's visibility note if needed) describing
purpose, parameters, return types, and errors semantics; include `@param` for name
and params/enrollmentMode on createDomain and `@param` for domainIds on the two
bulk methods, an `@returns` that documents the Promise shape including the data
array and bulk errors array, and a short `@remarks` or `@public` note to flag that
this is reference-facing and requires Docs-team review.

In `@packages/localizations/src/zh-CN.ts`:
- Around line 199-205: The zh-CN localization for configureSSO only includes a
subset for organizationDomainsStep (title, subtitle, formFieldLabel__domain,
formFieldInputPlaceholder__domain, formButtonPrimary__add), so domainSuggestion
and domainCard are missing and will fall back to en-US; to fix, add the missing
keys domainSuggestion and domainCard under organizationDomainsStep in the same
object (provide their Chinese strings) so zh-CN fully covers
configureSSO.organizationDomainsStep and avoids relying on the enUS fallback.

In `@packages/ui/src/components/ConfigureSSO/steps/index.ts`:
- Line 3: Remove the new re-export from the ConfigureSSO steps barrel: delete
the line exporting OrganizationDomainsStep from this index.ts and update any
call sites that currently import OrganizationDomainsStep from the barrel to
import directly from './OrganizationDomainsStep'; this keeps the symbol
OrganizationDomainsStep defined only in its module and avoids expanding the
index.ts surface that can introduce circular-dependency risk.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 554ffff4-747c-4743-b949-1cbb2a53eaae

📥 Commits

Reviewing files that changed from the base of the PR and between 5a28ac1 and df2ee32.

📒 Files selected for processing (91)
  • .changeset/curvy-hounds-fix.md
  • packages/clerk-js/src/core/resources/Organization.ts
  • packages/clerk-js/src/core/resources/OrganizationDomain.ts
  • packages/localizations/src/ar-SA.ts
  • packages/localizations/src/be-BY.ts
  • packages/localizations/src/bg-BG.ts
  • packages/localizations/src/bn-IN.ts
  • packages/localizations/src/ca-ES.ts
  • packages/localizations/src/cs-CZ.ts
  • packages/localizations/src/da-DK.ts
  • packages/localizations/src/de-DE.ts
  • packages/localizations/src/el-GR.ts
  • packages/localizations/src/en-GB.ts
  • packages/localizations/src/en-US.ts
  • packages/localizations/src/es-CR.ts
  • packages/localizations/src/es-ES.ts
  • packages/localizations/src/es-MX.ts
  • packages/localizations/src/es-UY.ts
  • packages/localizations/src/fa-IR.ts
  • packages/localizations/src/fi-FI.ts
  • packages/localizations/src/fr-FR.ts
  • packages/localizations/src/he-IL.ts
  • packages/localizations/src/hi-IN.ts
  • packages/localizations/src/hr-HR.ts
  • packages/localizations/src/hu-HU.ts
  • packages/localizations/src/id-ID.ts
  • packages/localizations/src/is-IS.ts
  • packages/localizations/src/it-IT.ts
  • packages/localizations/src/ja-JP.ts
  • packages/localizations/src/kk-KZ.ts
  • packages/localizations/src/ko-KR.ts
  • packages/localizations/src/mn-MN.ts
  • packages/localizations/src/ms-MY.ts
  • packages/localizations/src/nb-NO.ts
  • packages/localizations/src/nl-BE.ts
  • packages/localizations/src/nl-NL.ts
  • packages/localizations/src/pl-PL.ts
  • packages/localizations/src/pt-BR.ts
  • packages/localizations/src/pt-PT.ts
  • packages/localizations/src/ro-RO.ts
  • packages/localizations/src/ru-RU.ts
  • packages/localizations/src/sk-SK.ts
  • packages/localizations/src/sr-RS.ts
  • packages/localizations/src/sv-SE.ts
  • packages/localizations/src/ta-IN.ts
  • packages/localizations/src/te-IN.ts
  • packages/localizations/src/th-TH.ts
  • packages/localizations/src/tr-TR.ts
  • packages/localizations/src/uk-UA.ts
  • packages/localizations/src/utils/enUS_v4.ts
  • packages/localizations/src/vi-VN.ts
  • packages/localizations/src/zh-CN.ts
  • packages/localizations/src/zh-TW.ts
  • packages/shared/src/react/hooks/index.ts
  • packages/shared/src/react/hooks/useOrganizationDomains.shared.ts
  • packages/shared/src/react/hooks/useOrganizationDomains.tsx
  • packages/shared/src/react/stable-keys.ts
  • packages/shared/src/types/elementIds.ts
  • packages/shared/src/types/json.ts
  • packages/shared/src/types/localization.ts
  • packages/shared/src/types/organization.ts
  • packages/shared/src/types/organizationDomain.ts
  • packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOWizard.tsx
  • packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx
  • packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.navigation.test.tsx
  • packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx
  • packages/ui/src/components/ConfigureSSO/__tests__/ResetConnectionDialog.test.tsx
  • packages/ui/src/components/ConfigureSSO/domain/__tests__/organizationEnterpriseConnection.test.ts
  • packages/ui/src/components/ConfigureSSO/domain/organizationEnterpriseConnection.ts
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useOrganizationEnterpriseConnection.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/SamlCustomConfigureSteps.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/SamlGoogleConfigureSteps.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/SamlMicrosoftConfigureSteps.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/SamlOktaConfigureSteps.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/__tests__/SamlConfigureSteps.test.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/OrganizationDomainsStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx
  • packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
  • packages/ui/src/components/ConfigureSSO/steps/index.ts
  • packages/ui/src/components/OrganizationProfile/EnrollmentBadge.tsx
  • packages/ui/src/components/OrganizationProfile/OrganizationSecurityPage.tsx
  • packages/ui/src/customizables/elementDescriptors.ts
  • packages/ui/src/elements/contexts/index.tsx
  • packages/ui/src/internal/appearance.ts
💤 Files with no reviewable changes (2)
  • packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx
  • packages/ui/src/components/ConfigureSSO/domain/organizationEnterpriseConnection.ts

Comment thread packages/localizations/src/ar-SA.ts
Comment thread packages/localizations/src/uk-UA.ts Outdated
Comment thread packages/localizations/src/utils/enUS_v4.ts Outdated
Comment on lines +94 to +123
const createDomain = useCallback(
async (name: string) => {
let created = await organization?.createDomain(name, enrollmentMode ? { enrollmentMode } : undefined);

if (created && enrollmentMode === 'enterprise_sso') {
const prepared = await organization?.prepareOwnershipVerification([created.id]);
created = prepared?.data[0] ?? created;
}

await revalidate();
return created;
},
[organization, revalidate, enrollmentMode],
);

const prepareOwnershipVerification = useCallback(
async (domains: OrganizationDomainResource[]) => {
const prepared = await organization?.prepareOwnershipVerification(domains.map(domain => domain.id));
await revalidate();
return prepared;
},
[organization, revalidate],
);

const attemptOwnershipVerification = useCallback(
async (domains: OrganizationDomainResource[]) => {
const attempted = await organization?.attemptOwnershipVerification(domains.map(domain => domain.id));
await revalidate();
return attempted;
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always revalidate in mutation helpers, including error paths.

createDomain can successfully persist a domain (Line 96) and then throw on ownership preparation (Line 99), but cache invalidation only runs on the success path. That leaves stale UI and makes retries misleading (domain exists while list is stale). Same risk applies to the prepare/attempt helpers.

Proposed fix
   const createDomain = useCallback(
     async (name: string) => {
-      let created = await organization?.createDomain(name, enrollmentMode ? { enrollmentMode } : undefined);
-
-      if (created && enrollmentMode === 'enterprise_sso') {
-        const prepared = await organization?.prepareOwnershipVerification([created.id]);
-        created = prepared?.data[0] ?? created;
-      }
-
-      await revalidate();
-      return created;
+      try {
+        let created = await organization?.createDomain(name, enrollmentMode ? { enrollmentMode } : undefined);
+
+        if (created && enrollmentMode === 'enterprise_sso') {
+          const prepared = await organization?.prepareOwnershipVerification([created.id]);
+          created = prepared?.data[0] ?? created;
+        }
+
+        return created;
+      } finally {
+        await revalidate();
+      }
     },
     [organization, revalidate, enrollmentMode],
   );

   const prepareOwnershipVerification = useCallback(
     async (domains: OrganizationDomainResource[]) => {
-      const prepared = await organization?.prepareOwnershipVerification(domains.map(domain => domain.id));
-      await revalidate();
-      return prepared;
+      try {
+        return await organization?.prepareOwnershipVerification(domains.map(domain => domain.id));
+      } finally {
+        await revalidate();
+      }
     },
     [organization, revalidate],
   );

   const attemptOwnershipVerification = useCallback(
     async (domains: OrganizationDomainResource[]) => {
-      const attempted = await organization?.attemptOwnershipVerification(domains.map(domain => domain.id));
-      await revalidate();
-      return attempted;
+      try {
+        return await organization?.attemptOwnershipVerification(domains.map(domain => domain.id));
+      } finally {
+        await revalidate();
+      }
     },
     [organization, revalidate],
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/react/hooks/useOrganizationDomains.tsx` around lines 94 -
123, The mutation helpers createDomain, prepareOwnershipVerification, and
attemptOwnershipVerification currently only call revalidate on the success path;
move the revalidate call into a finally block (or ensure it always runs on both
success and error) so cache invalidation happens even if ownership
preparation/attempt throws; for createDomain specifically, perform the create
call, then try the prepareOwnershipVerification step, and in a finally call
revalidate before rethrowing any caught error (preserving the returned/created
value on success), and do the same pattern for prepareOwnershipVerification and
attemptOwnershipVerification so revalidate always runs regardless of errors.

Comment thread packages/shared/src/types/organization.ts Outdated
Comment thread packages/ui/src/components/OrganizationProfile/EnrollmentBadge.tsx
Comment thread packages/ui/src/internal/appearance.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant